#include "upgrade_client.hpp"

#include <string>
#include <data/app_config.hpp>
#include <cpr/cpr.h>
#include <json/json.hpp>
#include <fmt/format.h>
#include <filesystem>

#include "app/app_base.hpp"
#include "app/version.h"
#include "event/events.hpp"
#include "utils/app_utils.hpp"
#include "utils/path_utils.hpp"
#include "utils/md5.hpp"

#if defined(_WIN32)
#include <Windows.h>
#endif

static std::string upgrade_server_base_url;         // 检查更新的服务器网址
static std::atomic_bool is_checking{ false };

using defer = std::shared_ptr<void>;

#define MakeDefer(LAMBDA) defer _ ## __LINE__ (nullptr, LAMBDA)

static struct SoftwareUpdateInfo
{
    bool Valid{ false };

    std::string Version;
    std::string FileName;
    int64_t     FileSize{ 0 };
    std::string FilePath;
    std::string MD5;
    int         Node{ 0 };
    std::string Description;
    std::string Time;
    std::string Type;
    std::string EnglishName;
    std::string ChineseName;

    NLOHMANN_DEFINE_TYPE_INTRUSIVE(SoftwareUpdateInfo, Version, FileName, FileSize, FilePath, MD5, Node, Description, Time, Type, EnglishName, ChineseName)
} upgrade_info;

#define APP_NAME "DiaryApp"

constexpr auto MAX_CODE_NUM_CNT = 4;
static_assert(MAX_CODE_NUM_CNT * 8 <= 32);

static void ExtractVersionCode(const std::string& version_str, uint32_t(&code)[MAX_CODE_NUM_CNT + 1])
{
    memset(code, 0x00, sizeof(code)); // 全部填0

    uint32_t* pCnt = code;
    uint32_t* pCur = code + 1;

    for (auto const& c : version_str)
    {
        if (isdigit(c))
        {
            *pCur = (*pCur) * 10 + (c - '0');
        }
        else if (c == '.')
        {
            ++pCur;
            ++* pCnt;
        }

        if (pCur >= code + 5)
            break;
    }
    ++* pCnt;
}

static int64_t CompareVersion(const std::string& v1, const std::string& v2)
{
    if (v1.empty())
        return false;
    if (v2.empty())
        return true;

    uint32_t code1[MAX_CODE_NUM_CNT + 1];
    uint32_t code2[MAX_CODE_NUM_CNT + 1];

    static_assert(std::size(code1) == std::size(code2));

    static constexpr auto code_sum = [](const uint32_t(&code_arr)[MAX_CODE_NUM_CNT + 1]) -> int64_t
    {
        /// 版本号使用56位有符号整数表示，其中
        /// 63-56 保留，为0
        /// 55-48 主版本，即 VERSION_MAJOR
        /// 47-40 次版本，即 VERSION_MINOR
        /// 39-32 修订版本，即 VERSION_PATCH
        /// 31-0  构建号，可以理解为发布次数
        int64_t sum = 0;
        int shift = 48;
        for (uint32_t i = 1; i <= code_arr[0]; ++i)
        {
            if (i == MAX_CODE_NUM_CNT)
                sum |= static_cast<int64_t>(code_arr[i]); // 最后一个数字不需要移位
            else
            {
                sum |= (static_cast<int64_t>(code_arr[i]) << shift);
                shift -= 8;
            }
        }
        return sum;
    };

    ExtractVersionCode(v1, code1);
    ExtractVersionCode(v2, code2);

    return code_sum(code1) - code_sum(code2);
}

void check_upgrade_async(bool is_automatic)
{
    if (is_checking.load())
        return;

    init_upgrade_config(); // 有必要更新一下服务器配置
    App->Async([server = upgrade_server_base_url, is_automatic]
        {
            is_checking.store(true);
            MakeDefer([](...) { is_checking.store(false); });

            if (server.empty())
            {
                if (!is_automatic)
                {
                    SendError("请先在设置页面中填写升级服务器地址");
                }
                return;
            }

            upgrade_info.Valid = false;

            // make request
            const auto req_url = fmt::format("{}api/v1/softwares?type={}&version=", server, APP_NAME);
            if (auto response = cpr::Get(cpr::Url(req_url), cpr::ConnectTimeout(10000), cpr::Timeout(10000)); response.status_code == 200)
            {
                // 解析JSON，获取版本号
                const auto json = nlohmann::json::parse(response.text);
                json.get_to(upgrade_info);
                const std::string version_server = upgrade_info.Version;
                if (version_server.empty())
                {
                    // 无法获取版本号
                    SendError("无法检查更新，服务器返回无数据！");
                }
                else
                {
                    upgrade_info.Valid = true;
                    AppEvent::AppUpgradeEvent evt{};

                    // 版本比较
                    const std::string version_local = get_version_string();
                    evt.current_version = version_local;
                    evt.latest_version = version_server;
                    evt.release_note = upgrade_info.Description;
                    if (const auto cmp_result = CompareVersion(version_server, version_local); cmp_result > 0)
                    {
                        // 有升级可用
                        evt.update_flags = AppEvent::AppUpgradeEvent::eHasNewVersion;
                    }
                    else if (cmp_result == 0)
                    {
                        // 已经是最新版本
                        evt.update_flags = AppEvent::AppUpgradeEvent::eAlreadyLatest;
                    }
                    else
                    {
                        // 本地版本更高？
                        evt.update_flags = AppEvent::AppUpgradeEvent::eAlreadyLatest;
                        upgrade_info.Valid = false;
                    }

                    App->Bus()->postpone(evt);
                }
            }
            else
            {
                // 检查失败
                SendError(fmt::format("检查更新请求失败！ 代码：{}", response.status_code));
            }
        });
}

void init_upgrade_config()
{
    // 从软件配置中读取
    upgrade_server_base_url = Value(Config::KEY_UPGRADE_SERVER_URL);
    if (!upgrade_server_base_url.empty() && upgrade_server_base_url.back() != '/')
        upgrade_server_base_url.push_back('/');
    Value(Config::KEY_UPGRADE_SERVER_URL) = upgrade_server_base_url;
}

static bool FileCheck(const std::string& file_path, const std::string& MD5)
{
    auto const md5 = file_md5(file_path);
    return !md5.empty() && std::ranges::equal(md5, MD5, [](const auto& c1, const auto& c2)
        {
            return tolower(c1) == tolower(c2);
        });
}

struct DownloadData
{
    std::ostream* out;
    int64_t downloaded;
    int64_t total_length;
    int     last_notify;
};

static bool DownloadFileProgressCallback(std::string dat, intptr_t ud)
{
    auto* data = reinterpret_cast<DownloadData*>(ud);
    data->out->write(dat.data(), dat.length());
    data->downloaded += dat.length();

    const auto progress = 100 * data->downloaded / data->total_length;
    if (progress - data->last_notify > 5) {
        SendNotification(fmt::format("升级文件下载中，进度：{}%", progress), 0);
        data->last_notify = progress / 5 * 5;
    }

    return dat.length() > 0;
}

static bool DownloadAndCheck(const std::string& file_path, const std::string& base_url, const SoftwareUpdateInfo& info)
{
    // 先获取下载连接
    const auto res = cpr::Get(
        cpr::Url(fmt::format("{}api/v1/downloads", base_url)),
        cpr::VerifySsl(false),
        cpr::Parameters{ {"path", info.FilePath} },
        cpr::Timeout(10000),
        cpr::ConnectTimeout(10000)
    );

    if (res.status_code != 200)
        return false;

    std::ofstream ofs(file_path, std::ios::binary | std::ios::trunc | std::ios::out);
    if (!ofs.is_open())
        return false;

    DownloadData data{
        .out = &ofs,
        .downloaded = 0,
        .total_length = info.FileSize,
        .last_notify = 0
    };

    cpr::Session session;
    session.SetVerifySsl(cpr::VerifySsl{ false });

    // session.SetUrl(cpr::Url(fmt::format("{}{}", base_url, info.FilePath)));
    session.SetUrl(cpr::Url(res.text));
    auto const response = session.Download(cpr::WriteCallback{ &DownloadFileProgressCallback, (intptr_t)&data });
    ofs.close();
    return response.error.code == cpr::ErrorCode::OK && response.downloaded_bytes == info.FileSize && FileCheck(file_path, info.MD5);
}

static void StartInstall(const std::string& package_path)
{
#if defined(_WIN32)
    // 直接打开文件 和 安装目录
    auto wBinDir = fmt::detail::utf8_to_utf16(GetAppBinaryDirPath());
    auto wPackagePath = fmt::detail::utf8_to_utf16(package_path);

    ShellExecuteW(NULL, L"open", wBinDir.c_str(), NULL, NULL, SW_SHOWNORMAL);
    ShellExecuteW(NULL, L"open", wPackagePath.c_str(), NULL, NULL, SW_SHOWNORMAL);

    // 退出程序
    App->Exit();
#endif
    // Linux 先不实现
}

void download_latest_package(bool force_download)
{
    // async download file
    App->Async([info = upgrade_info, server = upgrade_server_base_url]
        {
            while (is_checking.load() == true)
            {
                // wait for check process
            }

            is_checking.store(true);
            MakeDefer([](...) { is_checking.store(false); });

            if (info.Valid) // 没有信息就退出
            {
                const auto download_path = GetTempFilePath(info.FileName.c_str());

                // 检查文件是否已经下载
                if (FileCheck(download_path, info.MD5))
                {
                    // 已经下载好了
                    SendNotificationEx("下载已完成", 0, "手动安装", [download_path]
                        {
                            StartInstall(download_path);
                        });
                }
                else
                {
                    if (DownloadAndCheck(download_path, server, info))
                    {
                        // 已经下载好了
                        SendNotificationEx("下载已完成", 0, "手动安装", [download_path]
                            {
                                StartInstall(download_path);
                            });
                    }
                    else
                    {
                        SendNotificationEx("下载失败了", 0,
                            "重试", []
                            {
                                download_latest_package(false);
                            },
                            "知道了", [] {});
                    }
                }
            }
        });
}
